interface的介紹會在這篇先告個段落,因為一個精美的golang底層source code,主體和主體的介接和設計,不是光用interface就能一口氣解釋全部。
包括embedded、pointer、goruitne&channel等等,甚至資料結構教的link list概念也會穿插其中,當這些概念都會的話,基本上就大部分看懂golang的底層設計方式。
筆者認為,interface是一個很重要的部分,而且深入了解這塊,對於其他知識主題的理解也很有幫助,逐一破解,有天source code就不是你的障礙,而是優秀的導師。
今天來將前面幾篇的介紹做個總結,順便再說兩個筆者認為是進階的設計方式。
承Day17 最後的範例程式碼改寫,如下面再貼一遍
// Employee 員工
type Employee struct {
id int
age int
}
// ITool 工具的method規格
type ITool interface {
IsCanUse() bool
}
// UseTool 使用工具
func (e *Employee) UseTool(t ITool) error {
isOk := t.IsCanUse()
if !isOk {
return errors.New("此工具使用壽命已盡,無法再使用")
}
return nil
}
其實外面的人來看你的code,若沒有用到Employee的UseTool(),其它他可能不會知道Employee(員工)與Tool(工具),兩種主體業務彼此的關係。
於是在他的腦袋中,要建立這兩者的理解,可能需要多費一點功夫。
若兩者主體是很緊密的關係,一開始就能用個方式宣告、暗示,無非在使用上會有莫大的幫助。
於是回到interface第一篇介紹的部分,DB struct裡面就放著connector的interface,現在就好理解。
DB一定要有個連線處理的業務主體,沒有這樣的連線主體,這個sql的package根本實際上沒路用,兩者本身在要做的事情就有很緊密的關係。
type DB struct {
waitDuration int64
connector driver.Connector
}
上面可以解讀成,DB擁有一個『屬於』它的connector主體業務,這主體業務是interface。
對於放在外面的interface只是規格,如我們的ITool。
// ITool 工具的method規格
type ITool interface {
IsCanUse() bool
}
筆者認為有三點是我們設計上要關心的:
誰實現符合這規格的主體?
什麼時候產生這個實體?
這個實體給誰使用?
這三個問題可以來解答,為什麼把connector這個interface放在DB裡面的設計關係。
不論誰來實現,DB到時候會知道是誰,或者提供方法,讓實現的實體傳入DB做使用。
不用在產生DB這個實體的時候,實現connector的實體就要準備好,記得interface的zero value嗎?是nil,一開始是nil完全沒有關係,等到時機到了,產生實現connector的實體,再塞進來就好,就也是實現lazy initialization的好方法。
擺明的,DB這個主體業務會用,其他主體會不會用不知道,也沒關係,但你看到DB struct的時候,知道DB會使用它。
sql package 提供一個叫Register()的方法,讓connector有實體後丟到DB使用的做法,看你是mysql driver 還是oricle driver的connector,DB不關心細節,只關心connector裡面定義好的method。
// Register makes a database driver available by the provided name.
// If Register is called twice with the same name or if driver is nil,
// it panics.
func Register(name string, driver driver.Driver) {
driversMu.Lock()
defer driversMu.Unlock()
if driver == nil {
panic("sql: Register driver is nil")
}
if _, dup := drivers[name]; dup {
panic("sql: Register called twice for driver " + name)
}
drivers[name] = driver
}
筆者忘記在哪個golang source code看到的使用方法,配合type assertion的使用,覺得也是可以參考一下。
我們知道interface,背後基本上還是會有個struct,這個struct有個符合interface的method,所以可以被視為某種interface類型。
如果我們的例子
// Tool 工具1
type Tool struct {
id string
name string
power float64
weight float64
remainUsedTime int // 剩下可使用次數
}
// IsCanUse 可否使用
func (t *Tool) IsCanUse() bool {
if t.remainUsedTime < 0 {
return false
}
return true
}
// Tool2 工具2
type Tool2 struct {
id string
name string
power float64
weight float64
UsedTime int // 使用過的次數
}
// IsCanUse 可否使用
func (t2 *Tool2) IsCanUse() bool {
if t2.UsedTime >= 10000 {
return false
}
return true
}
// ITool 工具的method規格
type ITool interface {
IsCanUse() bool
}
如果今天改造employee的struct如下,在實現iTool的實體,你可能不知道實際上是Tool1或者Tool2。
type Employee struct {
id int
iTool ITool
}
但是需要知道細部結構的時候,你可以撰寫某些判斷輔助,或設計上就某些根據你知道其實後面是哪種struct。
那麼就可以使用type assertion的方式,強制把interface轉成struct做使用。
if t, ok := iTool.(Tool2); !ok {
fmt.Println("type assertion 失敗")
}
不過筆者還是提醒一下,該使用方式看各位的需求,但interface的設計理念和struct的理念不同,想好再這麼做。
另外type assertion有個預先判斷最好,如上面的寫法,否則失敗是會發生panic。